#include <iostream>
#include <vector>
#include <string>
#include <boost/filesystem.hpp>
#include "util.hpp"

const std::string src_path = "data/raw_html";
const std::string res_path = "data/parse_html/parse.txt";

typedef struct DocInfo
{
    std::string title;   // 文档的标题
    std::string content; // 文档的正文内容
    std::string url;     // 文档在官网中的url
} DocInfo_t;

bool EnumFile(const std::string src_path, std::vector<std::string> *file_lists);
bool ParseFile(const std::vector<std::string> &file_lists, std::vector<DocInfo_t> *results);
bool SaveDoc(const std::vector<DocInfo_t> &results, const std::string res_path);

int main()
{
    std::vector<std::string> file_lists;

    // 第一步：递归式的把每个html文件名带路径保存到file_lists中，方便读取我们的文件内容。
    if (!EnumFile(src_path, &file_lists))
    {
        std::cerr << "enum file error!" << std::endl;
        return 1;
    }

    // 第二步:读取我们的html文件，并将它们解析
    std::vector<DocInfo_t> results;
    if (!ParseFile(file_lists, &results))
    {
        std::cerr << "parse file error!" << std::endl;
        return 2;
    }

    // 第三步:保存我们解析后的数据,保存到res_path路径所对应的文件中,使用\3作为每个文档的分隔符
    if (!SaveDoc(results, res_path))
    {
        std::cerr << "save doc error!" << std::endl;
        return 3;
    }
    return 0;
}

bool EnumFile(const std::string src_path, std::vector<std::string> *file_lists)
{
    // 命名空间简写并定义一个path对象。
    namespace fs = boost::filesystem;
    fs::path root_path(src_path);

    // 判断src_path是否合法，如果路径不存在，就没有必要往后走了
    if (!fs::exists(root_path))
    {
        std::cerr << src_path << "not exits!!!" << std::endl;
        return false;
    }

    // 定义一个空的递归迭代器用来判断结束，并且利用递归迭代器提取我们的文件名带路径
    fs::recursive_directory_iterator end;
    for (fs::recursive_directory_iterator iter(root_path); iter != end; iter++)
    {
        // 判断是否是一个合法的普通文件
        if (!fs::is_regular_file(*iter))
        {
            continue;
        }

        // 如果是一个普通文件，判断文件后缀是否是我们需要的.html
        if (iter->path().extension() != ".html")
        {
            continue;
        }

        // 仅仅为了测试我们的提取是否正确
        // std::cout << "DeBug:  " << iter->path().string() << std::endl;

        // 到这里我们的文件一定是合法的，因此加入我们的file_lists中
        file_lists->push_back(iter->path().string());
    }

    // 这里我们已经将所有html文件递归输出到了file_lists中
    return true;
}

bool ParseTitle(const std::string &file, std::string *title)
{
    // 由于我们要找的<title>和</title>中间的内容
    // 因此使用string的find方法，找到文件中的两个字段即可
    size_t begin = file.find("<title>");
    // 找不到直接返回
    if (begin == std::string::npos)
    {
        return false;
    }
    // 找不到直接返回
    size_t end = file.find("</title>");
    if (end == std::string::npos)
    {
        return false;
    }

    // 走到这里表示我们两个字段都已经找到，只需要begin += 上 ”<title>“字符串的大小，就会形成一个前闭后开的区间
    // 我们再使用string 的substr方法提取我们的title即可
    begin += std::string("<title>").size();

    // 这里begin已经指向我们的<title>后面的第一个字符
    // 我们只需从begin开始，截取我们的区间长度即可，因为是前闭后开区间，后减前即可算出长度
    *title = file.substr(begin, end - begin);
    return true;
}

bool ParseContent(const std::string &file, std::string *content)
{
    // 去标签，提取正文内容
    // 这里使用到我们的简易状态机
    enum status
    {
        LABLE,
        CONTENT
    };


    status s = LABLE;
    // 以字符遍历我们的文件
    for (char c : file)
    {
        // 每一个html文件进来一定是处于标签中
        switch (s)
        {
        case LABLE:
            // 如果遍历到'>'，表示我们的标签遍历完了，需要更改状态
            if (c == '>')
                s = CONTENT;
            break;
        case CONTENT:
            // 可能前一个标签遍历完之后又进入新的标签，因此我们需要检查.
            if (c == '<')
                s = LABLE;
            else
            {
                if (c == '\n') // 这里不要我们的\n是因为后续\n会作为html文件解析之后的文本分隔符
                    c = ' ';
                content->push_back(c);
            }
            break;
        default:
            break;
        }
    }

    return true;
}

bool ParseUrl(const std::string &file_path, std::string *url)
{
    // 我们的boost官网的url
    std::string url_head = "https://www.boost.org/doc/libs/1_84_0/doc/html";
    // 我们项目中需要访问的html文件名字
    std::string url_tail;

    // data/raw_html/xxx.html，/xxx.html即为我们要找的
    // 解析文件路径，找到src_path后的文件名,pos需要加上我们的src_path的长度
    size_t pos = 0;
    pos += src_path.size();
    // 找到后，提取出来即可
    url_tail += file_path.substr(pos); // url_tail = /xxx.html

    *url = url_head + url_tail;
    return true;
}

//for debug
void ShowDoc(const DocInfo_t &doc)
{
    std::cout << doc.title << std::endl;
    std::cout << doc.content << std::endl;
    std::cout << doc.url << std::endl;
}

bool ParseFile(const std::vector<std::string> &file_lists, std::vector<DocInfo_t> *results)
{

    // 获取每一个文件的路径
    for (std::string file_path : file_lists)
    {
        // 读取文件到file
        std::string file;
        if (!ns_util::FileUtil::ReadFile(file_path, &file))
        {
            continue;
        }

        DocInfo_t doc;
        // 解析我们的文件，提取title
        if (!ParseTitle(file, &doc.title))
        {
            continue;
        }

        // 解析我们的文件，提取content
        if (!ParseContent(file, &doc.content))
        {
            continue;
        }
        // 解析我们的文件，提取url
        if (!ParseUrl(file_path, &doc.url))
        {
            continue;
        }

        // 仅仅为了我们的测试
        // ShowDoc(doc);
        // break;

        // 走到这里表示我们的文件已经成功提取所有内容，我们需要写入到results中
        results->push_back(std::move(doc)); // 这里使用push_back会进行拷贝参数，花很多时间，我们换取另外一种方法
    }
    return true;
}


bool SaveDoc(const std::vector<DocInfo_t> &results, const std::string res_path)
{
//定义我们文档中的分割符：title\3conten\3url\n
#define SEP '\3'

    //以写文件流打开我们的文件，并且以二进制方式写入
    std::ofstream out_file(res_path,std::ios::out | std::ios::binary);
    //检查文件流是否打开成功
    if(!out_file.is_open())
    {
        std::cerr << "open file" << res_path << " error" << std::endl;
        return false;
    }

    //遍历我们解析好的文档
    for(auto& doc_t : results)
    {
        std::string out;
        out += doc_t.title;
        out += SEP;
        out += doc_t.content;
        out += SEP;
        out += doc_t.url;
        out += '\n';

        //写入到文件中，二进制写入
        out_file.write(out.c_str(),out.size());
    }

    //关闭流
    out_file.close();
    return true;
}